#include <math.h>
#include "Texture.h"
#include "Camera.h"
#include "Shapes2D/Point2D.h"
#include "GameLib.h"
#include "Logger.h"
#include "GameController.h"
#include "../OpenGL.h"

#define CAMERA_FREE_MOVEMENT    0
#define CAMERA_LOOK_AT_POINT    1
#define CAMERA_LOCK_ON_OBJECT   2

#define DEFAULT_CAMERA_ANGLE	45.0f
#define DEFAULT_NEAR			1.0f
#define DEFAULT_FAR				200.f

Camera::Camera(){
    freeMovement();
    lensAngle = DEFAULT_CAMERA_ANGLE;
    near = DEFAULT_NEAR;
    far = DEFAULT_FAR;
	state = CAMERA_FREE_MOVEMENT;
	lockDirectionHorizontal=false;
	lockDirectionVertical=false;
}
Camera::~Camera(){}
Camera&			Camera::lookAt(Point3D &pt){
    lap = pt;
    state = CAMERA_LOOK_AT_POINT;
    return *this;
}
Camera&			Camera::lockOnObject(Object *obj){
    loObj = obj;
    distance = position.distance(loObj->position);
    state = CAMERA_LOCK_ON_OBJECT;
    return *this;
}
Camera&			Camera::freeMovement(){
    state = CAMERA_FREE_MOVEMENT;
    return *this;
}
Camera&			Camera::rotateAroundR(double horizontal, double vertical){
    switch (state)
    {
        case CAMERA_FREE_MOVEMENT:
        case CAMERA_LOCK_ON_OBJECT:
            direction.rotateR(horizontal,vertical);
			recalculatePosition();
            break;
        case CAMERA_LOOK_AT_POINT:
			recalculateDirection();//just to make sure the position wasn't moved
			recalculateDistance();
			direction.rotateR(horizontal, vertical);
			recalculatePosition();
            break;
        default:break;
    }
    return *this;
}
Camera&			Camera::rotateAroundD(double horizontal, double vertical){
    return rotateAroundR(DEG2RAD(horizontal), DEG2RAD(vertical));
}
Direction3D&	Camera::recalculateDirection(){
    switch (state)
    {
		case CAMERA_LOCK_ON_OBJECT:
			position.direction(loObj->position, direction);
			break;
		case CAMERA_LOOK_AT_POINT:
			position.direction(lap, direction);
			break;
		default:break;
    }
    return direction;
}
Camera&			Camera::zoom(double zoomDist){
	distance+=zoomDist;
	recalculatePosition();
	return *this;
}
Camera&			Camera::zoomExp(double zoomExp){
	distance*=zoomExp;
	recalculatePosition();
	return *this;
}
Camera&			Camera::setDistance(double distance){
	this->distance = distance;
	recalculatePosition();
	return *this;
}
double			Camera::getDistance(){
	return distance;
}
Camera&			Camera::setNear(double near){
	this->near = near;
	return *this;
}
Camera&			Camera::setFar(double far){
	this->far = far;
	return *this;
}
Camera&			Camera::setLensAngle(double angle){
	this->lensAngle = angle;
	return *this;
}
double			Camera::getNear(){
	return near;
}
double			Camera::getFar(){
	return far;
}
double			Camera::getLensAngle(){
	return lensAngle;
}
void			Camera::recalculatePosition(){
	switch (state)
	{
		case CAMERA_LOOK_AT_POINT:
			position = lap;
			break;
		case CAMERA_LOCK_ON_OBJECT:
			position = loObj->position;
			break;
		default:
			return;
	}
	direction.invert();
	position.move(direction, distance);
	direction.invert();
}
void			Camera::recalculatePosDirH(){
	double dir;
	dir = direction.getRotationVerticalR();
	
	switch (state)
	{
		case CAMERA_LOOK_AT_POINT:
			position.direction(lap, direction);
			position = lap;
			break;
		case CAMERA_LOCK_ON_OBJECT:
			position.direction(loObj->position, direction);
			position = loObj->position;
			break;
		default:
			return;
	}
	direction.setRotationR(direction.getRotationHorizontalR(), dir);
	direction.invert();
	position.move(direction, distance);
	direction.invert();
}
void			Camera::recalculatePosDirV(){
	double dir;
	dir = direction.getRotationVerticalR();
	switch (state)
	{
		case CAMERA_LOOK_AT_POINT:
			position.direction(lap, direction);
			position = lap;
			break;
		case CAMERA_LOCK_ON_OBJECT:
			position.direction(loObj->position, direction);
			position = loObj->position;
			break;
		default:
			return;
	}
	direction.setRotationR(dir, direction.getRotationVerticalR());
	direction.invert();
	position.move(direction, distance);
	direction.invert();
}
void			Camera::recalculateDistance(){
	switch (state)
	{
		case CAMERA_LOOK_AT_POINT:
			distance = position.distance(lap);
			break;
		case CAMERA_LOCK_ON_OBJECT:
			distance = position.distance(loObj->position);
		default:
			break;
	}
}
Camera&			Camera::lockAngle(){
	lockDirectionHorizontal = true;
	lockDirectionVertical = true;
	return *this;
}
Camera&			Camera::lockAngleHorizontal(){
	lockDirectionHorizontal = true;
	return *this;
}
Camera&			Camera::lockAngleVertical(){
	lockDirectionVertical = true;
	return *this;
}
Camera&			Camera::unlockAngle(){
	lockDirectionHorizontal = false;
	lockDirectionVertical = false;
	return *this;
}
void			Camera::__cameraSetup(){
	if (lockDirectionHorizontal)
	{
		if (lockDirectionVertical)
		{recalculatePosition();}
		else
		{recalculatePosDirV();}
	}
	else
	{
		if (lockDirectionVertical)
		{recalculatePosDirH();}
		else
		{recalculateDirection();}
	}
	if (lensAngle <= 0) {errLog.print("Camera angle was 0! Setting it to the defualt!");lensAngle = DEFAULT_CAMERA_ANGLE;}
	if (far<near)
	{
		double t;
		errLog.print("Near and far roles are reversed!");
		t = near;
		near = far;
		far = t;
	}
	if (far==near){return;}
    gluPerspective(lensAngle,gameController.getWindowRatio(),near,far);
    gameLib.errListener("gluPerspective for the camera");
    switch (state)
    {
        case CAMERA_LOOK_AT_POINT:
            gluLookAt(position.getX(), position.getY(), position.getZ(),
                      lap.getX(), lap.getY(), lap.getZ(),
                      0, 0, 1);
			gameLib.errListener("gluLookAt() for the camera");
            break;
        case CAMERA_LOCK_ON_OBJECT:
			position = loObj->position;
			direction.invert();
			position.move(direction, distance);
			direction.invert();
            gluLookAt(position.getX(), position.getY(), position.getZ(),
                      loObj->position.getX(), loObj->position.getY(), loObj->position.getZ(),
                      0, 0, 1);
            break;
        case CAMERA_FREE_MOVEMENT:
			//NEED TO DO
            break;
        default:
            errLog.print("Unknown camera state");
            break;
    }
}
